﻿/*
 * Created by SharpDevelop.
 * User: Skanky
 * Date: 13/04/2011
 * Time: 17:04
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Configuration;
using ICSharpCode.SharpZipLib.Zip;
using Microsoft.Win32;

namespace Fw_pck_patcher
{
	struct fileTableEntry
	{
	    public string filePath; // 260 byte
	    public int fileDataOffset;
	    public int fileDataDecompressedSize;
	    public int fileDataCompressedSize;
	}
	
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	public partial class MainForm : Form
	{
		public string FwInstallPath = "c:\\Perfect World Entertainment\\Forsaken World FRlol\\resources\\";
		
		public int BufferSize = 4096;
		
		public int nb = 0;
		
		private int KEY_1 = 566434367;
		private int KEY_2 = 408690725;
		
		public MainForm()
		{
			//
			// The InitializeComponent() call is required for Windows Forms designer support.
			//
			InitializeComponent();
			
			string curInstDir = System.Configuration.ConfigurationManager.AppSettings["fwfolder"];
			
			if ( !Directory.Exists(curInstDir) ){
			
				string Reg = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\SMUpdatePack\\DefaultIcon", "", "0");
				if ( Reg.Length > 15 ){
					Reg = Reg.Substring(0, Reg.Length - 13);
					CheckInstallFolder(Reg);
				}
			}
			else{
				FwInstallPath = curInstDir + "resources\\";
				label1.Text = "FW Folder: "+curInstDir;
			}
			//
			// TODO: Add constructor code after the InitializeComponent() call.
			//
		}
		
		void CheckInstallFolder(string path){
			button1.Enabled = false;
			if ( Directory.Exists(path+"resources\\help") ){
				savePref("fwfolder", path);
				FwInstallPath = path + "resources\\";
				button1.Enabled = true;
				label1.Text = "FW Folder: "+path;
			}
			else {
				label1.Text = "Forsaken World Folder not founded";
			}
		}
		
		void Button1Click(object sender, EventArgs e)
		{
			if ( openFileDialog1.ShowDialog() != DialogResult.OK )
				return;
			string[] zipFiles = openFileDialog1.FileNames;
			nb = 0;
			for (int i = 0; i < zipFiles.Length; i++) {
				FileStream fileStreamIn = new FileStream(zipFiles[i], FileMode.Open, FileAccess.Read);
          		 ZipInputStream zipInStream = new ICSharpCode.SharpZipLib.Zip.ZipInputStream(fileStreamIn);
          		 ZipEntry entry = zipInStream.GetNextEntry();
	            while(entry != null){
	            	string fileName = zipRealName(entry.Name);
	            	string[] fileExp = fileName.Split('\\');
	            	if ( File.Exists(FwInstallPath+fileExp[0]+".pck") ){
	            	
		            	MemoryStream ms = new MemoryStream();
		            	int size;
				          byte[] buffer = new byte[BufferSize];
				           do
				           {
				               size = zipInStream.Read(buffer, 0, buffer.Length);
				               //ms.SetLength((int)ms.Position+size);
				               ms.Write(buffer, 0, size);
				               
				               //fileStreamOut.Write(buffer, 0, size);
				           } while (size > 0);
				           byte[] data = (byte[])ms.ToArray();
				           fileName = fileName.Substring(fileExp[0].Length+1);
				           /*
				           BinaryWriter bw = new BinaryWriter(File.Create("c:\\"+fileName));
							bw.Write(data);
							bw.Close();
							*/
				           PatchPck(fileExp[0]+".pck", fileName, data);
	            	}
			           
			           entry = zipInStream.GetNextEntry();
	            }
          		 zipInStream.Close();
          		 fileStreamIn.Close();
			}
			
			
			
			label1.Text = nb.ToString() + " files patched";
		}
		
		string zipRealName(string name){
			Encoding utf = Encoding.UTF8;
         	Encoding def = Encoding.GetEncoding(ZipConstants.DefaultCodePage);
         	byte[] a = def.GetBytes(name);
         	return utf.GetString(a);
         }
		
		void PatchPck(string pckFilename, string filename, byte[] data)
		{
			
			FileStream fs = new FileStream(FwInstallPath+pckFilename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
		    BinaryWriter bw = new BinaryWriter(fs);
			BinaryReader br = new BinaryReader(fs);
			
			
			fs.Seek(-8,SeekOrigin.End);
			int entryCount = br.ReadInt32();
			
			fs.Seek(-272,SeekOrigin.End);
			int fileTableOffset = br.ReadInt32() ^ KEY_1;
			fs.Seek(fileTableOffset,SeekOrigin.Begin);
			
			int entrySize;
			fileTableEntry[] fileTable = new fileTableEntry[entryCount];
			bool[] toEdit = new bool[entryCount];
			int t = 0;
			int editedFiles = 0;
			for (int i = 0; i < entryCount; i++) {
				entrySize = br.ReadInt32() ^ KEY_1;
                entrySize = br.ReadInt32() ^ KEY_2;
                
                byte[] hdata = br.ReadBytes(entrySize);
               // 
                if(entrySize < 276)
		        {
		            fileTable[i] = readTableEntry(hdata, entrySize, true);
		            t++;
		        }
		        // no zlib
		        else
		        {
		            fileTable[i] = readTableEntry(hdata, entrySize, false);
		        }
		        
		    }
			
			textBox1.Text += entryCount.ToString()+"\r\n";
			
			fs.Seek(fileTableOffset,SeekOrigin.Begin); // useless ??
			
			int lastEntryPosition = fileTableOffset;
			
			//int filesCount = (int)files.Length;
			
			for (int i = 0; i < entryCount; i++) {
				if ( fileTable[i].filePath != filename ) continue;
				nb++;
				int newFileDataDecompressedSize = (int)data.Length;
				
				MemoryStream ms = new MemoryStream();
				ICSharpCode.SharpZipLib.Zip.Compression.Deflater defl = new ICSharpCode.SharpZipLib.Zip.Compression.Deflater(1,false);
		    	Stream s = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(ms,defl);
				s.Write(data, 0, data.Length);
				s.Close();
				byte[] dataComp = (byte[])ms.ToArray();
				ms.Close();
				int newFileDataCompressedSize = (int)dataComp.Length;
				
				// test de remplacer un gros png par un truc tout petit incompresible
				
				if ( newFileDataCompressedSize <= fileTable[i].fileDataCompressedSize ){
					
					fs.Seek(fileTable[i].fileDataOffset,SeekOrigin.Begin);
					bw.Write(dataComp);
					textBox1.Text += fileTable[i].filePath+" Replaced\r\n";
				}
				else {
					fileTable[i].fileDataOffset = lastEntryPosition;
					fs.Seek(fileTable[i].fileDataOffset,SeekOrigin.Begin);
					bw.Write(dataComp);
					lastEntryPosition = (int)fs.Position;
					textBox1.Text += fileTable[i].filePath+" Moved to end\r\n";
				}
				fileTable[i].fileDataCompressedSize = newFileDataCompressedSize;
				fileTable[i].fileDataDecompressedSize = newFileDataDecompressedSize;
				
				//ShowProgress(entryCount, i+1);
				
			}
			
			fs.Seek(lastEntryPosition,SeekOrigin.Begin);
			
			fileTableOffset = (int)fs.Position;
		    
		    for (int i = 0; i < entryCount; i++) {
		    	
		    	data = new byte[276];
		        byte[] name = System.Text.Encoding.GetEncoding(936).GetBytes(fileTable[i].filePath);
		        Array.Copy(name, data, name.Length);
		        BitConverter.GetBytes(Convert.ToInt32(fileTable[i].fileDataOffset)).CopyTo(data, 260);
		        BitConverter.GetBytes(Convert.ToInt32(fileTable[i].fileDataDecompressedSize)).CopyTo(data, 264);
		        BitConverter.GetBytes(Convert.ToInt32(fileTable[i].fileDataCompressedSize)).CopyTo(data, 268);
		        
		        MemoryStream ms = new MemoryStream();
				ICSharpCode.SharpZipLib.Zip.Compression.Deflater defl = new ICSharpCode.SharpZipLib.Zip.Compression.Deflater(1,false);
		    	Stream s = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(ms,defl);
				s.Write(data, 0, data.Length);
				s.Close();
				byte[] dataComp = (byte[])ms.ToArray();
				entrySize = (int)dataComp.Length;
				
				bw.Write(entrySize ^ KEY_1);
		        bw.Write(entrySize ^ KEY_2);
		        
		        bw.Write(dataComp);
		        
		        //ShowProgress(entryCount, i+1);
		    }
			
			bw.Write((int)-1526153788);
		    bw.Write((short)2);
		    bw.Write((short)2);
		    bw.Write(fileTableOffset ^ KEY_1);
		    bw.Write((int)0);
		    bw.Write(System.Text.Encoding.GetEncoding(936).GetBytes("Angelica File Package, Perfect World."));
		    for (int i = 0; i < 215; i++) {
		    	bw.Write((byte)0);
		    }
		    bw.Write((int)-2060097592);
		    bw.Write((int)entryCount);
		    bw.Write((short)2);
		    bw.Write((short)2);
		    fs.SetLength(fs.Position);
		    int fileSize = (int)fs.Position;
		    fs.Seek(4, SeekOrigin.Begin);
		    bw.Write(fileSize);
		    
			br.Close();
			bw.Close();
			fs.Close();
			
			//textBox1.Text += editedFiles.ToString()+" File(s) patched";
			//label4.Text = filesCount.ToString()+" file(s) patched";
		}
		
		fileTableEntry readTableEntry(byte[] data, int size, bool compressed)
		{
			fileTableEntry fte = new fileTableEntry();
			MemoryStream ms = new MemoryStream(data,0,data.Length);

			byte[] filePath = new byte[260];
		    if(compressed)
		    {
		    	Stream s2 = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(ms);
		    	byte[] dataDec = new byte[276];
		    	s2.Read(dataDec,0,276);
		    	ms = new MemoryStream(dataDec,0,dataDec.Length);
		    	//ms = mms;
		        /*
		    	wxByte* pBuffer = buffer;
		        buffer = deflate(buffer, size, 276);
		        wxDELETEA(pBuffer);
		        */
		    }
		    ms.Read(filePath, 0, 260);
		    
		    byte[] fileDataOffset = new byte[4];
		    byte[] fileDataDecompressedSize = new byte[4];
		    byte[] fileDataCompressedSize = new byte[4];
		    
		    ms.Read(fileDataOffset, 0, 4);
		    ms.Read(fileDataDecompressedSize, 0, 4);
		    ms.Read(fileDataCompressedSize, 0, 4);
		    
		    //wxMemoryInputStream mis(buffer, 276);
		    //wxDataInputStream bis(mis);
		    //ms.Read(fte.filePath, 260);
		    //fte.filePath = BitConverter.ToString(filePath, 0, 260);
		    int length = 0;
		    for (int i = 0; i < 260; i++) {
		    	if ( filePath[i] == (byte)0 ){
		    		length = i;
		    		break;
		    	}
		    }
		    byte[] filePathOk = new byte[length];
		    Array.Copy(filePath, filePathOk, length);
		    //fte.filePath = Encoding.UTF8.GetString(filePathOk);
		    fte.filePath = Encoding.GetEncoding(936).GetString(filePathOk);
		    
		    fte.fileDataOffset = BitConverter.ToInt32(fileDataOffset, 0);
		    fte.fileDataDecompressedSize = BitConverter.ToInt32(fileDataDecompressedSize, 0);
		    fte.fileDataCompressedSize = BitConverter.ToInt32(fileDataCompressedSize, 0);
		    //if ( fte.fileDataDecompressedSize < fte.fileDataCompressedSize)
		   		//textBox1.Text += "\r\n__"+fte.fileDataDecompressedSize.ToString()+" -- "+fte.filePath+" -- ";
		    return fte;
		}
		
		void savePref(string key, string value)
		{
			Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
			config.AppSettings.Settings.Remove(key);
			config.AppSettings.Settings.Add(key, value);
			config.Save(ConfigurationSaveMode.Modified);
			ConfigurationManager.RefreshSection("appSettings");

		}
		
		
		
		void SelectFWInstallFolderToolStripMenuItemClick(object sender, EventArgs e)
		{
			if ( folderBrowserDialog1.ShowDialog() == DialogResult.OK ){
				string path = folderBrowserDialog1.SelectedPath+"\\";
				CheckInstallFolder(path);
			}
		}
	}
}
